feat(core): add NIP-05 over Namecoin (.bit) resolver#391
Draft
mstrofnone wants to merge 2 commits into
Draft
Conversation
Adds a transport-free parsing module plus an NDK-integrated resolver for NIP-05 identifiers rooted in the Namecoin blockchain (`.bit`). - core/src/user/nip05namecoin.ts: identifier validation, NamecoinAddress parser, JSON extraction matching the Kotlin/Swift/Go/Rust references byte-for-byte (local-part priority: exact -> "_" -> first valid), ElectrumX scripthash helpers, and OP_NAME_UPDATE script encode/decode. - core/src/user/nip05namecoin-resolver.ts: getNamecoinNip05For honors ndk.queuesNip05 and ndk.cacheAdapter the same way getNip05For does. Transport is injected by the caller; NDK stays isomorphic. - core/src/ndk/index.ts: optional ndk.namecoinResolver field. - core/src/user/index.ts: NDKUser.fromNip05 routes .bit/d/id identifiers through the Namecoin path when a resolver is configured; the DNS path is preserved for every other identifier. - core/src/index.ts: re-exports the public surface. Adds 34 vitest cases covering identifier validation, parser semantics, simple+extended JSON shapes, script encode/decode round-trips, ElectrumX scripthash math, and the resolver end-to-end with a mock. No new runtime dependencies; uses the existing @noble/hashes/sha2.js. Upstream NIP PR: nostr-protocol/nips#2349 Companions: nostr-tools#533, rust-nostr/nostr#1367.
Namecoin's 520-byte per-name limit makes apex records crowded.
ifa-0001 §"import" lets a record delegate shared blocks into a sibling
name via an `"import"` key on the JSON value. Without import-chain
handling, NIP-05 lookups against records that use this pattern (the
canonical demo target `testls.bit` is one) silently fail: the resolver
sees the apex value, finds no `nostr` field, and returns null — never
consulting the imported sibling that actually carries the
`nostr.names` block.
This commit adds `expandImports`, a recursive merger that honors every
shape of the spec:
- Four `import` value shapes (bare string, single-array,
pair-array, canonical array-of-arrays).
- Subdomain-selector walk via the imported value's `map` tree
(exact label > `*` wildcard > empty default, DNS-rightmost-first).
- Importer-wins shallow merge with `null` as a delete marker.
- Recursion up to a configurable depth (default 4, the spec
minimum); deeper chains truncated, importer's own fields retained.
- Cycle protection via a visited `(name|selector)` set.
- Lenient I/O: lookup nulls, throws, or malformed JSON resolve to
`{}` so transient ElectrumX hiccups don't kill resolution.
- `import` key stripped from the final merged result.
`getNamecoinNip05For` calls `expandImports` between parsing the apex
value and extracting the `nostr` field. Non-import records cost zero
extra I/O (the trigger short-circuits before any lookup).
21 new vitest cases cover every behaviour above, including the
`testls.bit` real-world pattern end-to-end through a fake ElectrumX
transport.
6c4e284 to
ad4e45a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a NIP-05 over Namecoin (
.bit) resolver to@nostr-dev-kit/ndkasa sibling to the existing DNS-based
getNip05Forpath. Identifiers likealice@example.bit,example.bit,d/example, andid/alicecan beresolved against the Namecoin blockchain via a caller-supplied ElectrumX
transport.
The resolver also honours the
ifa-0001
§"import" mechanism, so records that delegate shared blocks into a
sibling name (e.g. the canonical demo target
testls.bit, whose apexrecord is up against the 520-byte per-name limit and delegates its
nostr.namesblock todd/testls) resolve correctly without thecaller having to know about the indirection. Non-import records pay
zero extra I/O.
Tracking the in-flight NIP draft and its other implementations:
mstrofnone/nostrlib-nip05-namecoinThe parser semantics (local-part priority: exact →
_→ first valid;d/vsid/namespacing; tolerated leadingnostr:prefix; simple"nostr": "hex"vs extended"nostr": { names, relays, nip46 }JSONshapes) match those references byte-for-byte.
Design choices
@noble/hashes/sha2.jsfor the ElectrumX scripthash. No
nostr-tools-style additions.bundling a WSS client is undesirable. The parsing module exposes
buildNameIndexScript,electrumScriptHash, andparseNameUpdateScriptso callers can drive any ElectrumX transportthey like.
getNamecoinNip05Foraccepts an injected resolverfunction and otherwise reads
ndk.namecoinResolver.node:imports, noimport 'ws', no Node-onlyglobals.
TextEncoderis available on every NDK target; a manualencoder is provided as a fallback.
NDKUser.fromNip05only routesidentifiers through Namecoin when (a) the identifier matches the
Namecoin predicate and (b) a resolver is configured. Every other
identifier still flows through
getNip05Forunchanged.nip05.tsis untouched.
ifa-0001
"import"supportThe second commit on this branch (
6c4e2847) addsexpandImports, a recursive merger that handles every shape of thespec:
importvalue shapes — bare string, single-array,pair-array, canonical array-of-arrays — all normalise to the
canonical form.
maptree(exact label >
*wildcard > empty default, DNS-rightmost-first).nullas a delete marker(semantic suppression per ifa-0001).
deeper chains are silently truncated and the importer's own fields
are retained.
(name|selector)set scoped to onetop-level expansion.
null, throws, or malformed JSON resolve to{}so transient ElectrumX hiccups don't kill resolution.importkey is stripped from the merged result.The expansion runs once between parsing the apex value and extracting
the
nostrfield, so the existing extractor sees a richer mergedobject without any further changes. Non-import records short-circuit
before any lookup and cost zero extra I/O.
New public symbols
From
@nostr-dev-kit/ndk:isValidNamecoinIdentifier/isDotBitNamecoinAddress(withparse,toString,electrumScriptHash)extractNostrFromValue,profilePointerFromRawJsonbuildNameIndexScript,electrumScriptHash,parseNameUpdateScriptDEFAULT_ELECTRUMX_SERVERSgetNamecoinNip05For,getNamecoinNip05UserexpandImports,DEFAULT_IMPORT_MAX_DEPTHNamecoinResolver,GetNamecoinNip05Opts,NamecoinNip05Extract,ElectrumXServer,NamecoinImportLookup,NamecoinValue,NIP05_NAMECOIN_REGEX_BIT,NIP05_NAMECOIN_REGEX_NAMESPACEDOn the
NDKclass:ndk.namecoinResolver?: NamecoinResolver— optional. When set,NDKUser.fromNip05("alice@example.bit", ndk)returns the Namecoinresult.
Caller-side resolver shape
The transport itself is intentionally left out of the npm package: it
varies per consumer (browser, React Native, Tauri/Electron) and the
ElectrumX server certificates are self-signed.
The same callback is reused for any
importtargets the apex valuedeclares — no extra wiring required on the caller side.
Tests
core/src/user/nip05namecoin.test.ts: 34 vitest cases coveringidentifier validation, parser semantics, simple + extended JSON
shapes (including
nip46extraction), script encode/decoderound-trips, ElectrumX scripthash math,
OP_PUSHDATA1framing, andthe resolver end-to-end with a mock transport.
core/src/user/nip05namecoin-import.test.ts: 21 new vitest casescovering every
importbehaviour from ifa-0001 — all four valueshapes, selector walks (DNS order, wildcards), importer-wins merge
with
nullsuppression, 4-level recursion, depth-budgettruncation, lookup failures (null / throw / malformed JSON),
malformed
importvalues, cycle breaking, and the end-to-endtestls.bitimport pattern through a fake ElectrumX transport.bun vitest run src/user/nip05namecoin.test.ts src/user/nip05namecoin-import.test.tsfrom
core/: 55 passed, 0 failed.coresuite: same pass/fail counts asmaster— noregressions from this PR (the unrelated pre-existing failures in
auth-retry,connectivity,fetchEvent-guardrails,ai-guardrails, anduser/indexexist identically onmaster).Marked as draft while the NIP draft and sibling implementations
land.